package model.ability;

import java.util.*;

import model.area_effect.SkillObject;
import model.core.GameScheduler;
import model.core.Notifiable;
import model.effects.Effect;
import model.entity.Entity;
import model.map.GameMap;
import model.map.MapPoint;
import model.map.MapPointTreeNode;
import model.map.Obstacle;

public class RangedAbilityHandler implements Notifiable{
	private GameMap map;
	private Queue<MapPointTreeNode> levelOrderQueue;
	private int currentLevel = 1;
	private int currentWidth = 1;
	private Effect baseEffect;
	private SkillObject prototype;
	private int duration;
	
	public RangedAbilityHandler(GameMap map) {
		this.map = map;
	}
	
	/**
	 * Method called to start a ranged ability.
	 * @param rangedMapAbility
	 * @param head
	 * @param propogationDelay
	 * @param effect
	 * @param caster
	 * @param originPoint
	 */
	public void run(RangedMapAbility rangedMapAbility, MapPointTreeNode head,
			int propogationDelay, Effect effect, Entity caster, MapPoint originPoint) {
		String name = rangedMapAbility.getName();
		String graphicID = rangedMapAbility.getGraphicID();
		this.duration = rangedMapAbility.getDuration();
		
		// instantiate the things used by the propogator
		this.levelOrderQueue = new LinkedList<MapPointTreeNode>();
		this.baseEffect = effect;
			
		// instantiate the prototype skillObject
		this.prototype = new SkillObject(name, graphicID, null,
			caster, originPoint, null);
		
		// make the first children
		for(MapPointTreeNode n : head.getChildren()){
			levelOrderQueue.add(n);
		}
		
		//  register the propogation
		GameScheduler.getInstance().register(propogationDelay, this);
	}

	/**
	 * At each point in the execution, propogate out one further layer.
	 */
	public void execute() {
		// if the queue is empty, then the tree is done executing
		if(this.levelOrderQueue.isEmpty()){
			GameScheduler.getInstance().unRegister(this);
			return;
		}
		
		for(int i = 0;i<this.currentWidth;i++){
			// get relevant info about current node
			MapPointTreeNode curNode = levelOrderQueue.remove();
			MapPoint currLocation = curNode.getMapPoint();
			SkillObject toPlace = this.prototype.clone();
			
			// calculate modifications to apply to the prototype
			double modification = 1 / (.5 * this.currentLevel);
			Effect toApply = this.baseEffect.clone();
			toApply.modifyParameter(modification);
			toPlace.setEffect(toApply);
			toPlace.setLocation(currLocation);
			
			// add skillobject to map and register it
			map.addSkillObjectToTile(toPlace, currLocation);
			GameScheduler.getInstance().registerOnce(toPlace, this.duration);
			
			// add children to the levelOrderQueue
			for(MapPointTreeNode t : curNode.getChildren()){
				MapPoint p = t.getMapPoint();
				List<Entity> entsOnTile = map.getEntitiesOnTile(p);
				List<Obstacle> obstacles = map.getObstaclesOnTile(p);
				
				// propogate if child square is empty 
				if(entsOnTile.isEmpty() && obstacles.isEmpty())
					levelOrderQueue.add(t);
			}
		}
		
		// incrementors
		if(this.currentLevel % 2 == 1)
			this.currentWidth+=2;
		++this.currentLevel;
	}
}
